package edu.cmu.ece.ds.droidchat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;

import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import edu.cmu.ece.ds.droidchat.message.*;
import edu.cmu.ece.ds.droidchat.model.ChangeRequest;
import edu.cmu.ece.ds.droidchat.model.ChatSessionInfo;
import edu.cmu.ece.ds.droidchat.model.LoginSessionAttribute;
import edu.cmu.ece.ds.droidchat.model.StringConstants;
import edu.cmu.ece.ds.droidchat.model.UserInfo;

public class NetworkService extends Service {

	private static final String TAG = "NetworkService";
	private LoginSessionAttribute login_record;

	private NioClient client;
	private int updateRoundNumber = 0; // the latest location update round
										// number
	private static long lastUpdateTime; // the latest location update time
	private static long timeOut = 5000; // select() maximum blocking time

	private String[] server_ips;
	private int[] server_ports;
	private int num_servers; // number of servers, set in string.xml
	private int udp_port; // the port number for udp communications
	private boolean use_ssl_with_server = false; // set in string.xml

	// location related parameters
	private Location lastKnownLocation;

	public static final int CMU_MODE = 1;
	public static final int WATERFRONT_MODE = 2;
	public static final int REAL_MODE = 3;

	// 5000 forbes ave pittsburgh - CMU Address
	public static final double cmu_latitude = 40.44442;
	public static final double cmu_longitude = -79.94298;

	// 300 Waterfront Drive W Homestead - Waterfront AMC Theater Address
	public static final double waterfront_latitude = 40.40370;
	public static final double waterfront_longitude = -79.91966;

	private int locationReportMode = CMU_MODE; // default is report CMU location

	public void onCreate() {
		Log.e(TAG, "----- onCreate ---------");
		super.onCreate();

		num_servers = Integer.parseInt(this.getString(R.string.num_servers));

		populate_server_data();

		udp_port = Integer.parseInt(this.getString(R.string.udp_port));
		client = new NioClient(udp_port);

		int use_ssl = Integer.parseInt(this
				.getString(R.string.use_ssl_with_server));
		if (use_ssl == 1)
			use_ssl_with_server = true;

		lastKnownLocation = new Location(LOCATION_SERVICE);

		if (locationReportMode == CMU_MODE) {
			lastKnownLocation.setLongitude(cmu_longitude);
			lastKnownLocation.setLatitude(cmu_latitude);
		} else if (locationReportMode == WATERFRONT_MODE) {
			lastKnownLocation.setLongitude(waterfront_longitude);
			lastKnownLocation.setLatitude(waterfront_latitude);
		} else {
			// start with cmu in case GPS callback does not kick in
			lastKnownLocation.setLongitude(cmu_longitude);
			lastKnownLocation.setLatitude(cmu_latitude);
		}

		Thread t = new Thread(client);
		t.start();
		Log.d(TAG, "----- thread started ---------");
	}

	/**
	 * depending on 'num_servers' defined in strings.xml
	 * 
	 * this method reads and stores the server information into arrays
	 * 'server_ports' and 'server_ips'
	 */
	private void populate_server_data() {
		server_ports = new int[num_servers];
		server_ips = new String[num_servers];

		if (num_servers == 1) {
			// server A
			server_ips[0] = this.getString(R.string.serverAip);
			server_ports[0] = Integer.parseInt(this
					.getString(R.string.serverAport));
		} else if (num_servers == 2) {
			// servers A,B
			server_ips[0] = this.getString(R.string.serverAip);
			server_ports[0] = Integer.parseInt(this
					.getString(R.string.serverAport));
			server_ips[1] = this.getString(R.string.serverBip);
			server_ports[1] = Integer.parseInt(this
					.getString(R.string.serverBport));
		} else if (num_servers == 3) {
			// servers A,B,C
			server_ips[0] = this.getString(R.string.serverAip);
			server_ports[0] = Integer.parseInt(this
					.getString(R.string.serverAport));
			server_ips[1] = this.getString(R.string.serverBip);
			server_ports[1] = Integer.parseInt(this
					.getString(R.string.serverBport));
			server_ips[2] = this.getString(R.string.serverCip);
			server_ports[2] = Integer.parseInt(this
					.getString(R.string.serverCport));
		} else if (num_servers == 4) {
			// servers A,B,C,D
			server_ips[0] = this.getString(R.string.serverAip);
			server_ports[0] = Integer.parseInt(this
					.getString(R.string.serverAport));
			server_ips[1] = this.getString(R.string.serverBip);
			server_ports[1] = Integer.parseInt(this
					.getString(R.string.serverBport));
			server_ips[2] = this.getString(R.string.serverCip);
			server_ports[2] = Integer.parseInt(this
					.getString(R.string.serverCport));
			server_ips[3] = this.getString(R.string.serverDip);
			server_ports[3] = Integer.parseInt(this
					.getString(R.string.serverDport));
		}
	}

	/**
	 * This is the required method that allows this Service component to be
	 * bound by other components, usually an Activity that needs any network
	 * operations would bind to this Service component
	 */
	public IBinder onBind(Intent intent) {
		Log.e(TAG, "----- OnBind---------" + intent);

		LocationListener gpsListener = new LocationListener() {

			boolean locationChanged = false;

			public void onLocationChanged(Location location) {
				// Log.d(TAG, "onLocationChanged() is called");

				if (locationReportMode == REAL_MODE) {
					if (lastKnownLocation == null) {
						lastKnownLocation = location;
						locationChanged = true;
					}
				} // else we simply ignore the detected location
					// because we are simulating fake locations

				if (lastKnownLocation.getLatitude() == location.getLatitude()
						&& lastKnownLocation.getLongitude() == location
								.getLongitude())
					locationChanged = false;
				else
					locationChanged = true;

				lastKnownLocation = location;
			}

			public void onProviderDisabled(String provider) {
				/*
				 * Toast.makeText(getApplicationContext(), "Gps Disabled",
				 * Toast.LENGTH_SHORT).show();
				 */
			}

			public void onProviderEnabled(String provider) {
				/*
				 * Toast.makeText(getApplicationContext(), "Gps Enabled",
				 * Toast.LENGTH_SHORT).show();
				 */
			}

			public void onStatusChanged(String provider, int status,
					Bundle extras) {
			}
		};

		LocationManager locMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

		locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
				gpsListener);
		locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0,
				gpsListener);

		return mBinder;
	}

	/**
	 * prints out a debugging message so you know when some Activity unbinds
	 * this Service component
	 */
	public boolean onUnbind(Intent intent) {
		Log.e(TAG, "----- OnUnbind---------" + intent);
		return true;
	}

	/**
	 * prints out a debugging message so you know when some this Service
	 * component is being destroyed
	 */
	public void onDestroy() {
		Log.e(TAG, "----- onDestroy---------");
	}

	private final IBinder mBinder = new LocalBinder();

	public class LocalBinder extends Binder {
		NetworkService getService() {
			// Return this instance of TCPService so clients can call public
			// methods
			return NetworkService.this;
		}
	}

	/*************************** methods for clients ****************************/
	// clients here refer to those Activities that bind to this Service
	// component

	// isConnectedToServer
	public boolean isConnectedToServer() {
		return client.isConnectedToServer;
	}

	// login() - LogInActivity would call this method
	public void login(String username, String password) {
		Log.d(TAG, "login");

		// when register, use ClientLoginMessage
		ClientLoginMessage loginMessage = new ClientLoginMessage(username,
				password);

		Log.d(TAG, "C: Sending: '" + loginMessage + "'");
		try {
			client.sendTCP(loginMessage);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// logout() - DroidChatTabActivity would call this method
	public void logout() {
		Log.d(TAG, "logout");

		if (login_record != null) {
			// when client logs out, use LogOutRequestFromClient
			LogOutRequestFromClient r = new LogOutRequestFromClient(
					login_record.getLogin_session_id());
			try {
				client.sendTCP(r);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// register() - RegisterActivity would call this method
	public void register(String username, String password) {
		Log.d(TAG, "register");

		// when register, use ClinetRegisterMessage
		ClientRegisterMessage registerMessage = new ClientRegisterMessage(
				username, password);

		Log.d(TAG, "C: Sending: '" + registerMessage + "'");
		try {
			client.sendTCP(registerMessage);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// setLocationReportMode() - DroidChatTabActivity would call this method
	public void setLocationReportMode(int mode) {
		Log.d(TAG, "setting location report mode to " + mode);
		locationReportMode = mode;
	}

	// getUserPubKey() - ChatWindowActivity would call this method
	public void getUserPubKey(String username, String chatSessionID) {
		Log.d(TAG, "asking server for user's public key. " + username);
		
		if(login_record==null){
			// facebook is hot and sexy
			return;
		}
		
		CreateSessionInfoRequestMessage askPubKey = new CreateSessionInfoRequestMessage(
				chatSessionID, login_record.getLogin_session_id(), username);
		System.out.println("sending CreateSessionInfoResponseMessage ");
		System.out.println("chatSessionID " + chatSessionID);
		
		try {
			client.sendTCP(askPubKey);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// disconnectFromServer() - whichever Activity that logs the user out would
	// call this method
	public void disConnectFromServer() {

	}

	public LoginSessionAttribute getLoginAttribute() {
		return login_record;
	}

	/********** end of methods for clients ***********/

	private class NioClient implements Runnable {

		private Selector selector;
		private SocketChannel socketChannel;
		private SSLSocket serverSSLSocket;
		private SSLSession serverSSLSession;
		private LinkedList<ChangeRequest> pendingChanges;
		private LinkedList<ByteBuffer> pendingDataQueue;

		/*
		 * pendingDataQueueUDP stores what needs to be sent out on the UDP port
		 * [0] is the destination ip represented as a 4-byte int [1] is the
		 * actually message
		 */
		private LinkedList<ByteBuffer> pendingDataQueueUDP;

		/*
		 * pendingIncomingData stores what the server sends to me through the
		 * TCP pipe [0] is the byteSize header - should be 4 bytes, how many
		 * bytes is the message [1] is the actually message - should be as long
		 * as specified in 0th element
		 */
		private LinkedList<ByteBuffer> pendingIncomingData;

		private boolean isConnectedToServer;

		private DatagramChannel UDPChannel;

		Random rand = new Random();

		/** randomized load balancing **/
		// generate a random number to decide which server to connect to
		int random_num = rand.nextInt(num_servers);
		int serverNum;

		private LinkedList<ChangeRequest> reregisterChangeReqs = new LinkedList<ChangeRequest>();

		public NioClient(int my_udp_port) {
			try {
				selector = initSelector();
				isConnectedToServer = false;

				serverNum = random_num % num_servers;
				Log.d(TAG, "serverNum=" + serverNum);

				socketChannel = connectToServer();

				pendingChanges = new LinkedList<ChangeRequest>();
				pendingDataQueue = new LinkedList<ByteBuffer>();
				pendingIncomingData = new LinkedList<ByteBuffer>();
				pendingDataQueueUDP = new LinkedList<ByteBuffer>();

				UDPChannel = DatagramChannel.open();
				DatagramSocket UDPSsocket = UDPChannel.socket();
				UDPChannel.configureBlocking(false);
				Log.d(TAG, "  calling bind on port " + my_udp_port);
				UDPSsocket.bind(new InetSocketAddress(my_udp_port));
				UDPChannel.register(selector, SelectionKey.OP_READ);

				/*
				 * Log.d(TAG, "  num_servers is " + num_servers); Log.d(TAG,
				 * "  servers A ip is " + server_ips[0]);
				 */

				// Queue a channel registration since the caller is not the
				// selecting thread. As part of the registration we'll register
				// an interest in connection events. These are raised when a
				// channel is ready to complete connection establishment.
				synchronized (pendingChanges) {
					pendingChanges.add(new ChangeRequest(socketChannel,
							ChangeRequest.REGISTER, SelectionKey.OP_CONNECT));
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		private SocketChannel connectToServer() throws IOException {

			Log.d("connectToServer()", "" + serverNum);

			String ipAddr = server_ips[serverNum];
			int portNum = server_ports[serverNum];

			Log.d("connectToServer()", ipAddr);
			Log.d("connectToServer()", "" + portNum);

			// Create a non-blocking socket channel
			SocketChannel socketChannel = SocketChannel.open();
			socketChannel.configureBlocking(false);

			// Kick off connection establishment
			socketChannel.connect(new InetSocketAddress(ipAddr, portNum));
			return socketChannel;

		}

		public boolean isConnected() {
			return isConnectedToServer;
		}

		public void sendTCP(DroidChatMessage m) throws IOException {
			if (m instanceof ClientLoginMessage) {
				Log.d(TAG, "send called " + m);
			}

			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(m);
			oos.flush();

			// queue the data we want written
			synchronized (pendingDataQueue) {
				ByteBuffer header = ByteBuffer.allocate(4);
				int byteSize = baos.toByteArray().length;
				Log.d("send", "ClientLoginMessage is " + byteSize
						+ " bytes long");
				header.putInt(byteSize);
				pendingDataQueue.add(ByteBuffer.wrap(header.array()));
				pendingDataQueue.add(ByteBuffer.wrap(baos.toByteArray()));
			}

			Log.d(TAG, "inserted into pendingDataQueue");

			if (socketChannel.isConnected()) {
				synchronized (pendingChanges) {
					this.pendingChanges.add(new ChangeRequest(socketChannel,
							ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
				}
			}

			// Finally, wake up our selecting thread so it can make the required
			// changes
			this.selector.wakeup();
		}

		/*
		 * insert data to be sent out on the UDP port wakes up the selecting
		 * thread at the end so that selecting thread can perform the write
		 * operation on the DatagramChannel
		 */
		public void sendUDP(DroidChatMessage msg, String destination)
				throws IOException {

			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(msg);
			oos.flush();

			ByteBuffer dest = ByteBuffer.allocate(4);
			dest.putInt((int) Toolbox.ipToInt(destination));

			// queue data to be written on UDP channel
			synchronized (pendingDataQueueUDP) {
				pendingDataQueueUDP.add(ByteBuffer.wrap(dest.array()));
				pendingDataQueueUDP.add(ByteBuffer.wrap(baos.toByteArray()));
			}
			Log.d(TAG, "inserted into pendingDataQueueUDP");
			synchronized (pendingChanges) {
				this.pendingChanges.add(new ChangeRequest(UDPChannel,
						ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
			}
			this.selector.wakeup();
		}

		public void run() {
			try {
				while (true) {
					synchronized (pendingChanges) {
						for (ChangeRequest change : pendingChanges) {
							switch (change.type) {
							case ChangeRequest.CHANGEOPS:
								SelectionKey key = change.socket
										.keyFor(this.selector);
								key.interestOps(change.ops);
								break;
							case ChangeRequest.REGISTER:
								change.socket.register(this.selector,
										change.ops);
								Log.d(TAG, "REGISTER CHANGE REQUEST ");
								break;
							case ChangeRequest.REREGISTER:
								change.type = ChangeRequest.REGISTER;
								reregisterChangeReqs.add(change);
								break;
							}
						}
						pendingChanges.clear();
						pendingChanges.addAll(reregisterChangeReqs);
						reregisterChangeReqs.clear();
					}

					// select() returns either when channel is ready for some IO
					// operation
					// or timeOut has passed
					selector.select(timeOut);

					// check if it's time to do another location update with
					// server
					long timeDiff = System.currentTimeMillis() - lastUpdateTime;

					if (login_record != null && login_record.isLoggedIn()
							&& timeDiff >= timeOut) {

						int num = Toolbox.numNeighbors();
						Toolbox.checkRoundNumber(updateRoundNumber);

						if (Toolbox.numNeighbors() != num) {
							// tell NearbyUserListActivity to refresh the
							// neighbor list content
							broadcastUpdateNeighborList();
						}
						// it's time do another round of location update with
						// server
						sendLocationUpdateMessage();
					}

					// go through the channels that select() found to be ready
					// for some IO operation
					Set<SelectionKey> selectKeys = selector.selectedKeys();
					Iterator<SelectionKey> keys = selectKeys.iterator();

					while (keys.hasNext()) {
						SelectionKey key = keys.next();

						if (key.isConnectable()) {
							Log.d(TAG, " connectable ");
							boolean result = false;

							if (use_ssl_with_server) {
								result = finishConnectionSSL(key,
										server_ips[serverNum],
										server_ports[serverNum]);
							} else {
								result = finishConnection(key);
							}
							Log.d(TAG, " connect result is " + result);

							if (result == true) {
								isConnectedToServer = true;

								// Register an interest in writing on this
								// channel, only if there is data to be written
								synchronized (pendingDataQueue) {
									if (!pendingDataQueue.isEmpty()) {
										Log.d(TAG,
												" pendingDataQueue is not empty ");
										key.interestOps(SelectionKey.OP_WRITE);
									} else {
										if (use_ssl_with_server) {
											// the thing with SSL is that client
											// shall always
											// send something first to force the
											// SSL handshake
											SSLClientDummyMessage m = new SSLClientDummyMessage();
											client.sendTCP(m);
										}
										key.interestOps(SelectionKey.OP_READ);
									}
								}
							} else {
								// connection failed, try next server
								random_num++;
								serverNum = random_num % num_servers;
								socketChannel = connectToServer();

								// have to let select() know you're interested
								// in OP_CONNECT
								synchronized (pendingChanges) {
									pendingChanges.add(new ChangeRequest(
											socketChannel,
											ChangeRequest.REGISTER,
											SelectionKey.OP_CONNECT));
								}
							}

						} else if (key.isWritable()) {
							//Log.d(TAG, " writable ");

							if (key.channel() == UDPChannel) {
								Log.d(TAG, "UDP Writable");
								writeUDP(key);
							} else if (key.channel() == socketChannel) {
								//Log.d(TAG, "TCP Writeable");
								if (use_ssl_with_server) {
									writeTCPSSL(key);
								} else {
									writeTCP(key);
								}
							}
						} else if (key.isReadable()) {
							//Log.d(TAG, " readable ");

							if (key.channel() == UDPChannel) {
								Log.d(TAG, " UDP readable ");
								readUDP(key);
							} else if (key.channel() == socketChannel) {
								Log.d(TAG, " TCP readable ");
								try {
									int error = 0;

									if (use_ssl_with_server) {
										error = readTCPSSL(key);
									} else {
										error = readTCP(key);
									}

									if (error == -1) {
										Log.d(TAG,
												"Connection dropped. Probably server died");
										random_num++;
										serverNum = random_num % num_servers;
										socketChannel = connectToServer();

										synchronized (pendingChanges) {
											pendingChanges
													.add(new ChangeRequest(
															socketChannel,
															ChangeRequest.REGISTER,
															SelectionKey.OP_CONNECT));
										}
									}
								} catch (SocketException e) {

								}
							}

						}
						// the channel that's ready for IO operation has been
						// taken care of
						Log.d(TAG, " removing key ");
						keys.remove();
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// the selecting thread calls this method to perform IO on the channel
		public void writeTCP(SelectionKey key) throws IOException {
			SocketChannel socketChannel = (SocketChannel) key.channel();

			Log.d(TAG, "writeTCP()");
			synchronized (pendingDataQueue) {
				while (!pendingDataQueue.isEmpty()) {
					ByteBuffer buf = (ByteBuffer) pendingDataQueue.get(0);
					int bytesWritten = socketChannel.write(buf);
					//Log.d(TAG, bytesWritten + " bytes written");

					if (buf.remaining() > 0) {
						// ... or the socket's buffer fills up
						Log.e(TAG, "the socket's buffer fills up");
						break;
					}
					pendingDataQueue.remove(0);
				}

				if (pendingDataQueue.isEmpty()) {
					// We wrote away all data, so we're no longer interested
					// in writing on this socket. Switch back to waiting for
					// data.
					key.interestOps(SelectionKey.OP_READ);
				}
			}
		}

		public void writeUDP(SelectionKey key) throws IOException {
			DatagramChannel datagramChannel = (DatagramChannel) key.channel();

			synchronized (pendingDataQueueUDP) {
				while (!pendingDataQueueUDP.isEmpty()) {
					ByteBuffer dest = pendingDataQueueUDP.get(0);
					ByteBuffer data = pendingDataQueueUDP.get(1);

					String destination = Toolbox.intToIp(dest.getInt());
					InetSocketAddress target = new InetSocketAddress(
							destination, udp_port);
					datagramChannel.send(data, target);

					if (data.remaining() > 0) {
						// ... or the socket's buffer fills up
						Log.e(TAG, "the socket's buffer fills up");
						break;
					}

					pendingDataQueueUDP.remove();
					pendingDataQueueUDP.remove();
				}

				if (pendingDataQueueUDP.isEmpty()) {
					key.interestOps(SelectionKey.OP_READ);
				}
			}
		}

		public void readUDP(SelectionKey key) throws IOException {
			Log.d(TAG, "readUDP()");
			DatagramChannel ch = (DatagramChannel) key.channel();
			ByteBuffer temp = ByteBuffer.allocate(1024);

			SocketAddress sender = null;
			try {
				sender = ch.receive(temp);
			} catch (IOException e1) {
				e1.printStackTrace();
			}

			if (sender == null) {
				Log.d(TAG, "sender is null");
			} else {
				Log.d(TAG, "sender is " + sender.toString());
				// Log.d(TAG, "after receive position=" + temp.position());

				byte[] inputBuffer = new byte[temp.position()];
				temp.flip();
				temp.get(inputBuffer);

				ByteArrayInputStream bais = new ByteArrayInputStream(
						inputBuffer);
				ObjectInputStream ois = null;
				ois = new ObjectInputStream(bais);

				DroidChatMessage msg = null;
				try {
					msg = (DroidChatMessage) ois.readObject();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
					return;
				}

				try {
					handleResponse(msg);
				} catch (NoSuchPaddingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		public int readTCP(SelectionKey key) throws IOException {
			Log.d(TAG, " read() called ");

			SocketChannel socketChannel = (SocketChannel) key.channel();

			int numRead;

			if (pendingIncomingData.isEmpty()) {
				pendingIncomingData.add(ByteBuffer.allocate(4)); // [0] is the
																	// byteSize
																	// of the
																	// message

				numRead = socketChannel.read(pendingIncomingData.get(0));
				if (numRead == -1) {
					// Remote entity shut the socket down cleanly. Do the
					// same from our end and cancel the channel.
					key.channel().close();
					key.cancel();
					isConnectedToServer = false;
					return (-1);
				} else if (numRead == 4) {
					pendingIncomingData.get(0).flip();
					int byteSize = pendingIncomingData.get(0).getInt();
					Log.d(TAG, numRead
							+ " bytes read from server. Message byteSize:"
							+ byteSize);

					pendingIncomingData.add(ByteBuffer.allocate(byteSize)); // [1]
																			// is
																			// the
																			// actual
																			// message
					ByteBuffer actualMsg = pendingIncomingData.get(1);

					numRead = socketChannel.read(actualMsg);

					if (numRead == -1) {
						// Remote entity shut the socket down cleanly. Do the
						// same from our end and cancel the channel.
						key.channel().close();
						key.cancel();
						isConnectedToServer = false;
						return (-1);
					} else {
						Log.d(TAG, numRead + " bytes read from server");
						if (actualMsg.remaining() == 0) {
							// turn bytes into DroidChatMessage
							ByteArrayInputStream bais = new ByteArrayInputStream(
									actualMsg.array());
							ObjectInputStream ois = new ObjectInputStream(bais);
							DroidChatMessage m = null;
							try {
								m = (DroidChatMessage) ois.readObject();
							} catch (ClassNotFoundException e) {
								e.printStackTrace();
							}
							// Handle the response
							try {
								handleResponse(m);
							} catch (NoSuchPaddingException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							pendingIncomingData.clear();
						}
					}
				}
			} else {
				ByteBuffer byteSizeBuf = pendingIncomingData.get(0);
				int remaining = byteSizeBuf.remaining();

				if (remaining == 0) {
					// I already know how many bytes the message is
					ByteBuffer actualMsg = pendingIncomingData.get(1);
					numRead = socketChannel.read(actualMsg);

					if (numRead == -1) {
						// Remote entity shut the socket down cleanly. Do the
						// same from our end and cancel the channel.
						key.channel().close();
						key.cancel();
						isConnectedToServer = false;
						return (-1);
					} else {
						Log.d(TAG, numRead + " bytes read from server");
						if (actualMsg.remaining() == 0) {
							// turn bytes into DroidChatMessage
							ByteArrayInputStream bais = new ByteArrayInputStream(
									actualMsg.array());
							ObjectInputStream ois = new ObjectInputStream(bais);
							DroidChatMessage m = null;
							try {
								m = (DroidChatMessage) ois.readObject();
							} catch (ClassNotFoundException e) {
								e.printStackTrace();
							}
							// Handle the response
							try {
								handleResponse(m);
							} catch (NoSuchPaddingException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							pendingIncomingData.clear();
						}
					}
				} else {
					numRead = socketChannel.read(byteSizeBuf);
					if (numRead == -1) {
						// Remote entity shut the socket down cleanly. Do the
						// same from our end and cancel the channel.
						key.channel().close();
						key.cancel();
						System.out.println("numRead=-1");
						pendingIncomingData.remove(socketChannel);
						return (-1);
					} else if (numRead == remaining) {
						byteSizeBuf.flip();
						int byteSize = byteSizeBuf.getInt();

						// finished reading in the byteSize
						Log.d(TAG, "Message byteSize:" + byteSize);

						pendingIncomingData.add(ByteBuffer.allocate(byteSize)); // [1]
																				// is
																				// the
																				// actual
																				// message
						ByteBuffer actualMsg = pendingIncomingData.get(1);

						numRead = socketChannel.read(actualMsg);
						if (numRead == -1) {
							// Remote entity shut the socket down cleanly. Do
							// the
							// same from our end and cancel the channel.
							key.channel().close();
							key.cancel();
							System.out.println("numRead=-1");
							pendingIncomingData.remove(socketChannel);
							return (-1);
						} else {
							if (actualMsg.remaining() == 0) {
								// turn bytes into DroidChatMessage
								ByteArrayInputStream bais = new ByteArrayInputStream(
										actualMsg.array());
								ObjectInputStream ois = new ObjectInputStream(
										bais);
								DroidChatMessage m = null;
								try {
									m = (DroidChatMessage) ois.readObject();
								} catch (ClassNotFoundException e) {
									e.printStackTrace();
								}
								// Handle the response
								try {
									handleResponse(m);
								} catch (NoSuchPaddingException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
								pendingIncomingData.clear();
							}
						}
					}
				}
			}
			return 0;
		}

		// upon receiving a DroidChatMessage from
		// DroidChat server, call this method
		private void handleResponse(DroidChatMessage m) throws NoSuchPaddingException {
            if (m instanceof ClientRegisterResponseMessage) {
                ClientRegisterResponseMessage mm = (ClientRegisterResponseMessage) m;
                Log.d(TAG, " ClientRegisterResponseMessage=" + mm);
                Log.d(TAG, " in handleResponse(): " + mm.getUsername());
                broadcastRegisterResponse(mm);
            } else if (m instanceof ClientLoginResponseMessage) {
                ClientLoginResponseMessage mm = (ClientLoginResponseMessage) m;
                Log.d(TAG, " ClientLoginResponseMessage=" + mm);
                Log.d(TAG, " in handleResponse(): " + mm.getUsername());
                broadcastLoginResponse(mm);

                login_record = new LoginSessionAttribute(mm.getLoginSessionID(),mm.getUsername());
                try {
                    client.sendTCP(new ClientPublicKeyMessage(mm
                            .getLoginSessionID(), login_record.getPublicKey()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (m instanceof NeighborInfoMessage) {
                NeighborInfoMessage mm = (NeighborInfoMessage) m;
                Log.d(TAG, " NeighborInfoMessage=" + mm);
                Log.d(TAG, " in handleResponse(): " + mm.getUsername());
                Log.d(TAG, " in response to round: " + mm.getRound());

                if (mm.getRound() == updateRoundNumber) {
                    Log.d(TAG, " matches with updateRoundNumber:"
                            + updateRoundNumber);
                    broadcastNeighborInfo(mm);
                    if (mm.getRound() == updateRoundNumber) {
                        Log.d(TAG, " matches with updateRoundNumber:"
                                + updateRoundNumber);
                        broadcastNeighborInfo(mm);
                    }
                }

            } else if (m instanceof ChatInvitationMessage) {
                ChatInvitationMessage cim = (ChatInvitationMessage) m;
                Log.d(TAG, "In handleResponse");
                Log.d(TAG, "ChatInvitationMessage=" + cim);
                // Handle the response

            } else if (m instanceof ChatInvitationResponseMessage) {
                ChatInvitationResponseMessage cirm = (ChatInvitationResponseMessage) m;
                Log.d(TAG, "In handleResponse");
                Log.d(TAG, "ChatInvitationResponseMessage=" + cirm);
                // Handle the response
            }  else if (m instanceof CreateSessionInfoResponseMessage) {
            	Log.d(TAG, "CreateSessionInfoResponseMessage");
                
            	try {
                	CreateSessionInfoResponseMessage temp = (CreateSessionInfoResponseMessage) m;

                	Toolbox.putCreationKey(temp);
                	
                	UserInfo u = Toolbox.retrieveUserInfo(temp.getUsername());
                	
                	if(u!=null){
                		/*client.sendUDP(new EncryptCreateSessionInvitationMessage(
                    		login_record.encryptothers(
                    		new CreateSessionInvitationMessage(temp.getChatSessionID(),
                    				login_record.getUsername(), temp.getUsername())
                    		, temp.getPublic_modulus(), temp.getPublic_exponent()
                    		)),u.getUserIP());*/
                		System.out.println("sending CreateSessionInvitationMessage");
                		System.out.println("my username: " + login_record.getUsername());
                		System.out.println("invitee: " + temp.getUsername());
                		System.out.println("chat session id = " + temp.getChatSessionID());
                		
                		CreateSessionInvitationMessage invitation = new CreateSessionInvitationMessage(
                				temp.getChatSessionID(),
                				login_record.getUsername(),
                				temp.getUsername());
                		client.sendUDP(invitation, u.getUserIP());
                	}
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else if (m instanceof EncryptCreateSessionInvitationMessage) {
            	Log.d(TAG, "EncryptCreateSessionInvitationMessage");
                try {
                	CreateSessionInvitationMessage temp=(CreateSessionInvitationMessage)

                	login_record.decrypt(((EncryptCreateSessionInvitationMessage)m).getInvitation());
                	
                	//UserInfo u = Toolbox.retrieveUserInfo(temp.getSender_username());

                	if(temp.getSender_username()==null){
                		System.out.println("getSender_username() is null");
                	}
                	
                	/* if someone sent you CreateSessionInvitationMessage, 
                	    then he should be your neighbor as well */
                	UserInfo u = Toolbox.retrieveUserInfoByUsername(temp.getSender_username());

                	if(u==null){
                		System.out.println("u is null");
                	}
                	
                	if(temp.getSender_username()!=null && u!=null)
                	{
	                    client.sendUDP(new CreateSessionInvitationResponseMessage(
	                    		temp.getChatSessionID(),
	                    		login_record.encryptothers("like 1 means agree?",temp.getPublic_modulus()
	                    				,temp.getPublic_exponent()),login_record.getUsername()),u.getUserIP());
                	}
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (m instanceof CreateSessionInvitationResponseMessage) {
            	Log.d(TAG, "CreateSessionInvitationResponseMessage");
            		//here we should create a session
                	//this should be also put in some hash table,right?
                	//try {
                		CreateSessionInvitationResponseMessage temp = ((CreateSessionInvitationResponseMessage)m);
                		
                		///ArrayList<BigInteger> pubKey = Toolbox.getCreationKey(temp.getChatSessionID());
						// [0] is exponent
                		// [1] is modulus
                		
                		Toolbox.addParticipant(temp.getUsername(), temp.getChatSessionID());
                		ChatSessionInfo info = Toolbox.getSessionAttr(temp.getChatSessionID());
						UserInfo u = Toolbox.retrieveUserInfo(temp.getUsername());
						
						if(u!=null){
							System.out.println(" u is not null ");
							info.setPending(false);
						}
						
							/*client.sendUDP(new CreateSessionKeyMessage(temp.getChatSessionID(),
									
									login_record.encryptothers(
											info,
											pubKey.get(1),
											pubKey.get(0))),u.getUserIP());*/
					/*} catch (IOException e) {
						e.printStackTrace();
                }*/
            } else if (m instanceof CreateSessionKeyMessage) {
            	Log.d(TAG, "CreateSessionKeyMessage");
            	ChatSessionInfo newattr=(ChatSessionInfo)
				login_record.decrypt(((CreateSessionKeyMessage)m).getEncryptAttr());
            	Toolbox.addNewChatSession(newattr);
            } else if (m instanceof GeneralChatMessage) {
            	Log.d(TAG, "GeneralChatMessage");
            } else if (m instanceof CreateSessionInvitationMessage) {
            	System.out.println("CreateSessionInvitationMessage");
            	CreateSessionInvitationMessage invitation = (CreateSessionInvitationMessage)m;
            	
            	/* if someone sent you CreateSessionInvitationMessage, 
        	    then he should be your neighbor as well */
            	UserInfo u = Toolbox.retrieveUserInfoByUsername(invitation.getSender_username());

            	if(u==null){
            		System.out.println("u is null");
            		return;
            	}
        	
            	if (Toolbox.getSessionAttr(invitation.getChatSessionID())!=null) {
            		// you are already in the chat session ---> weird
            		return;
            	}
            	
            	if(invitation.getSender_username()!=null && u!=null){
            		
            		System.out.println("chat session id = " + invitation.getChatSessionID());
            		
            		broadcastChatInvitation(invitation);
            		
            		CreateSessionInvitationResponseMessage invitationResponse 
            			= new CreateSessionInvitationResponseMessage(
            					invitation.getChatSessionID(),
            					null,
            					login_record.getUsername());
            		System.out.println("sending CreateSessionInvitationResponseMessage");
            		System.out.println("my username " + login_record.getUsername());
            		Log.d("username", login_record.getUsername());
            		
            		try {
						client.sendUDP(invitationResponse, u.getUserIP());
					} catch (IOException e) {
						e.printStackTrace();
					}
            		
            		/*client.sendUDP(new CreateSessionInvitationResponseMessage(
            				invitation.getChatSessionID(),
                		login_record.encryptothers("like 1 means agree?",invitation.getPublic_modulus()
                				,invitation.getPublic_exponent()),login_record.getUsername()),u.getUserIP());
                	 */
            	}
            }
        }

		/**
		 * increment 'updateRoundNumber' send location update message to server
		 * with new round number update 'lastUpdateTime'
		 * 
		 * note that 'locationReportMode' affects what location will be updated
		 * to the server
		 */
		private void sendLocationUpdateMessage() {
			if (locationReportMode == CMU_MODE) {
				lastKnownLocation.setLongitude(cmu_longitude);
				lastKnownLocation.setLatitude(cmu_latitude);
			} else if (locationReportMode == WATERFRONT_MODE) {
				lastKnownLocation.setLongitude(waterfront_longitude);
				lastKnownLocation.setLatitude(waterfront_latitude);
			} // else in REAL_MODE, we don't manually set the values

			ClientUpdateLocationMessage locationMessage = new ClientUpdateLocationMessage(
					lastKnownLocation.getLatitude(),
					lastKnownLocation.getLongitude(),
					login_record.getLogin_session_id());

			locationMessage.setRound_number(++updateRoundNumber);
			lastUpdateTime = System.currentTimeMillis();
			try {
				client.sendTCP(locationMessage);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		/*
		 * send broadcast for ClientLoginResponseMessage received the intended
		 * receiver of this broadcast is LoginActivity
		 */
		private void broadcastLoginResponse(ClientLoginResponseMessage mm) {
			Intent intent = new Intent(
					StringConstants.ACTION_CLIENT_LOGIN_RESPONSE);
			intent.putExtra(StringConstants.EXTRA_USER_NAME, mm.getUsername());
			intent.putExtra(StringConstants.EXTRA_RESPONSE, mm.getResponse());
			intent.putExtra(StringConstants.EXTRA_LOGIN_SESSION_ID,
					mm.getLoginSessionID());
			Log.d(TAG, " broadcastLoginResponse() is called ");
			sendBroadcast(intent);
		}

		/*
		 * send broadcast for ClientRegisterResponseMessage received the
		 * intended receiver of this broadcast is RegisterActivity
		 */
		private void broadcastRegisterResponse(ClientRegisterResponseMessage mm) {
			Intent intent = new Intent(
					StringConstants.ACTION_CLIENT_REGISTER_RESPONSE);
			intent.putExtra(StringConstants.EXTRA_USER_NAME, mm.getUsername());
			intent.putExtra(StringConstants.EXTRA_RESPONSE, mm.getResponse());
			Log.d(TAG, " broadcastRegisterResponse() is called ");
			sendBroadcast(intent);
		}

		/*
		 * send broadcast for CreateSessionInvitationMessage received 
		 *  the intended receiver of this broadcast is DroidChatTabActivity
		 */
		private void broadcastChatInvitation(CreateSessionInvitationMessage invitation){
			Intent intent = new Intent(StringConstants.ACTION_CHAT_INVITATION);
			
		}
		
		/*
		 * send broadcast for NeighborInfoMessage received intended receiver of
		 * this broadcast is NearbyUsersListActivity
		 */
		private void broadcastNeighborInfo(NeighborInfoMessage mm) {
			Intent intent = new Intent(
					StringConstants.ACTION_NEIGHBOR_INFO_MESSAGE);
			intent.putExtra(StringConstants.EXTRA_USER_NAME, mm.getUsername());
			intent.putExtra(StringConstants.EXTRA_USER_IP, mm.getUserIp());
			// intent.putExtra(StringConstants.EXTRA_ROUND_NUMBER,
			// mm.getRound());
			Log.d(TAG, " broadcastNeighborInfo() is called ");

			if (mm.getRound() == updateRoundNumber) {
				UserInfo u = Toolbox.retrieveUserInfoByUsername(mm
						.getUsername());

				if (u == null) {
					u = Toolbox.retrieveUserInfo(mm.getUsername());
					// TODO: the client should make a connection to either the
					// server or the discovered user to retrieve his UserInfo.
					// This should be done in Toolbox.retrieveUserInfo.
					u.setUsername(mm.getUsername());
					u.setNickName(mm.getUsername());
					u.setUserIP(mm.getUserIp());
					u.setRound(mm.getRound());

					u.setStatus("");
					Toolbox.putToUserInfoHashtable(u);
				} else {
					// just update the round number
					u.setRound(mm.getRound());
					u.setUserIP(mm.getUserIp());
				}
			}

			sendBroadcast(intent);
		}

		/*
		 * send broadcast when hashtable 'userInfoHashtable' in ToolBox has been
		 * changed
		 * 
		 * intended receiver of this broadcast is NearbyUsersListActivity
		 */
		private void broadcastUpdateNeighborList() {
			Intent intent = new Intent(
					StringConstants.ACTION_NEIGHBOR_INFO_MESSAGE);
			Log.d(TAG, " broadcastUpdateNeighborList() is called ");
			sendBroadcast(intent);
		}

		private Selector initSelector() throws IOException {
			// Create a new selector
			Selector socketSelector = SelectorProvider.provider()
					.openSelector();
			return socketSelector;
		}

		private boolean finishConnection(SelectionKey key) throws IOException {
			SocketChannel socketChannel = (SocketChannel) key.channel();

			boolean connected = false;

			// Finish the connection. If the connection operation failed
			// this will raise an IOException.
			try {
				connected = socketChannel.finishConnect();
			} catch (IOException e) {
				// Cancel the channel's registration with our selector
				key.cancel();
			}

			return connected;
		}

		/************* SSL related methods *************************/
		private boolean finishConnectionSSL(SelectionKey key, String ip,
				int port) throws IOException {
			SocketChannel socketChannel = (SocketChannel) key.channel();

			// Finish the connection. If the connection operation failed
			// this will raise an IOException.
			try {
				socketChannel.finishConnect();
			} catch (IOException e) {
				// Cancel the channel's registration with our selector
				key.cancel();
				return false;
			}

			Socket socket = socketChannel.socket();
			registerSocketSSL(socket, ip, port, true);
			return true;
		}

		private void registerSocketSSL(Socket socket, String host, int port,
				boolean client) throws IOException {
			// "Upgrade" the new socket to an SSLSocket
			SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory
					.getDefault();
			SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
					host, port, true);

			Log.d("registerSocket. host", host);
			Log.d("registerSocket. port", "" + port);

			// Ensure that when we start reading on this socket it
			// acts the part of the server during the SSL handshake.
			sslSocket.setUseClientMode(client);
			serverSSLSocket = sslSocket;
			Log.d("serverSSLSocket", "" + serverSSLSocket);
		}

		/**
		 * return false means the sslSession is not established return true
		 * means the sslSession is established
		 * 
		 * @throws SSLException
		 */
		private boolean configureSSLSocket(Socket socket, SSLSocket sslSocket)
				throws SSLException {
			int sslHandshakeTimeout = 30000;

			if (serverSSLSession == null) {
				// no SSL Session established with server yet
				Log.d(TAG, "no SSL Session established with server yet");

				try {
					sslSocket.setSoTimeout(sslHandshakeTimeout);
				} catch (SocketException e) {
					e.printStackTrace();
				}

				// Get the SSL session. This forces a handshake and is used
				// to indicate that we've performed the handshake for this
				// SSLSocket.
				Log.d(TAG, "calling getSession()");
				SSLSession session = sslSocket.getSession();
				Log.d(TAG, "done calling getSession()");

				serverSSLSession = session;
				if (session.getId() != null && session.getId().length != 0) {
					Log.d("SSL session details", "" + session);
					Log.d(TAG, "valid");
				} else {
					Log.d(TAG, "invalid");
					if (sslSocket.getUseClientMode()) {
						throw new SSLException(
								"SSL session handshake failed (is the server SSL enabled?)");
					}
				}
			}
			try {
				sslSocket.setSoTimeout(500);
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return true;
		}

		private int readTCPSSL(SelectionKey key) throws IOException {
			Log.d(TAG, "readTCPSSL()");

			SocketChannel socketChannel = (SocketChannel) key.channel();

			key.cancel();
			key.channel().configureBlocking(true);

			try {
				configureSSLSocket(socketChannel.socket(), serverSSLSocket);
			} catch (SSLException e) {
				// SSL handshake fails
				Log.e(TAG, "SSL handshake with server fails");
				return -1;
			}

			InputStream is = serverSSLSocket.getInputStream();
			int numRead;

			if (pendingIncomingData.isEmpty()) {
				pendingIncomingData.add(ByteBuffer.allocate(4)); // [0] is the
																	// byteSize
																	// of the
																	// message
				byte[] header = new byte[4];

				try {
					numRead = is.read(header, 0, header.length);
				} catch (SocketTimeoutException e) {
					// The read timed out so we're done.
					numRead = 0;
				} catch (IOException e) {
					Log.e("IOException ", "" + e);
					return -1;
				}
				Log.d(TAG, "numRead=" + numRead);
				if (numRead == -1) {
					return -1;
				} else {
					pendingIncomingData.get(0).put(header, 0, numRead);
					if (pendingIncomingData.get(0).remaining() == 0) {
						pendingIncomingData.get(0).flip();
						int byteSize = pendingIncomingData.get(0).getInt();
						pendingIncomingData.add(ByteBuffer.allocate(byteSize)); // [1]
																				// is
																				// the
																				// actual
																				// message
					}
				}
			} else {
				ByteBuffer byteSizeBuf = pendingIncomingData.get(0);
				int remaining = byteSizeBuf.remaining();
				if (remaining == 0) {
					ByteBuffer actualMsg = pendingIncomingData.get(1);
					byte[] dataBuf = new byte[actualMsg.remaining()];
					try {
						numRead = is.read(dataBuf, 0, dataBuf.length);
					} catch (SocketTimeoutException e) {
						// The read timed out so we're done.
						numRead = 0;
					} catch (IOException e) {
						Log.e("IOException ", "" + e);
						// when this happens, we want to close the socket and
						// try connecting to the next server
						return -1;
					}
					Log.d(TAG, "numRead=" + numRead);
					if (numRead == -1) {
						return -1;
					} else {
						actualMsg.put(dataBuf, 0, numRead);
						if (actualMsg.remaining() == 0) {
							ByteArrayInputStream bais = new ByteArrayInputStream(
									actualMsg.array());
							ObjectInputStream ois = new ObjectInputStream(bais);
							DroidChatMessage m = null;
							try {
								m = (DroidChatMessage) ois.readObject();
							} catch (ClassNotFoundException e) {
								e.printStackTrace();
							}
							// Hand the data off to our worker thread
							pendingIncomingData.clear();
							try {
								handleResponse(m);
							} catch (NoSuchPaddingException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}
					}
				} else {
					byte[] remainingMessageHeader = new byte[remaining];
					try {
						numRead = is.read(remainingMessageHeader, 0,
								remainingMessageHeader.length);
					} catch (SocketTimeoutException e) {
						// The read timed out so we're done.
						numRead = 0;
					} catch (IOException e) {
						// when this happens, we want to close the socket and
						// try connecting to the next server
						Log.e("IOException ", "" + e);
						return -1;
					}
					Log.d(TAG, "numRead=" + numRead);
					if (numRead == -1) {
						return -1;
					} else {
						pendingIncomingData.get(0).put(remainingMessageHeader,
								0, numRead);
						if (pendingIncomingData.get(0).remaining() == 0) {
							pendingIncomingData.get(0).flip();
							int byteSize = pendingIncomingData.get(0).getInt();
							pendingIncomingData.add(ByteBuffer
									.allocate(byteSize)); // [1] is the actual
															// message
						}
					}
				}
			}

			// if the method has not returned up to this point, then we are
			// still interested in READING
			key.channel().configureBlocking(false);

			// Queue a channel reregistration
			Log.d(TAG, "Queue a channel reregistration");
			synchronized (pendingChanges) {
				pendingChanges.add(new ChangeRequest(socketChannel,
						ChangeRequest.REREGISTER, SelectionKey.OP_READ));
			}
			return numRead;
		}

		private int writeTCPSSL(SelectionKey key) {
			SocketChannel socketChannel = (SocketChannel) key.channel();
			Socket socket = socketChannel.socket();

			try {
				key.cancel();
				key.channel().configureBlocking(true);

				try {
					configureSSLSocket(socket, serverSSLSocket);
				} catch (SSLException e) {
					Log.e(TAG, "SSL handshake with server fails");
					return -1;
				}

				OutputStream os = serverSSLSocket.getOutputStream();

				// Write until there's no more data ...
				while (!pendingDataQueue.isEmpty()) {
					ByteBuffer buf = pendingDataQueue.get(0);
					System.out.println(" writing " + buf.capacity());
					os.write(buf.array());
					// System.out.println(" removing ");
					pendingDataQueue.remove(0);
				}

				// We've written the data, reregister the channel
				key.channel().configureBlocking(false);

				Log.d(TAG, "Queue a channel reregistration");

				synchronized (pendingChanges) {
					pendingChanges.add(new ChangeRequest(socketChannel,
							ChangeRequest.REREGISTER, SelectionKey.OP_READ));
				}

			} catch (IOException e) {
				e.printStackTrace();
				return -1;
			}

			return 1;
		}

		/************* end of SSL related methods *************************/
	}
}
